﻿namespace EFS.RC.CC.Tools.Hunters
{
	/// <summary>
	/// Hunters queue
	/// </summary>
	internal class Queue : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		/// Match run
		private static object matchSyncAccess = new object();
		private static System.Boolean matchRunning = false;
		private static System.Boolean matchSchedule = false;

		// sync access
		private object syncAccess = new object();

		// agents hunter queue
		private System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent> agentsHuntQueue = new System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent>();

		// agents
		private System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent> availableAgentsPresences = new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent>();

		// internal counter class
		private class Counters
		{
			// Synk acess
			private object syncAccess = new object();

			// List of agents hunt in this counter
			private System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent> agentHuntList = new System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent>();

			// average
			private System.Int64 averageWatingTime = 0;

			// last reset
			private System.DateTime lastResetDate = System.DateTime.Now.Date;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="phoneUri"></param>
			public Counters(System.String phoneUri)
			{
				PhoneUri = phoneUri;
				TotalCalls = 0;
				TotalAcceptedCalls = 0;
				TotalDroppedCalls = 0;
			}

			/// <summary>
			/// do 24h reset
			/// </summary>
			public void Do24hReset()
			{
				// s4h reset
				if ((EFS.RC.CC.Config.Settings.ResetCountersAt == System.DateTime.Now.Hour) && (System.DateTime.Now.Date != lastResetDate))
				{
					// keep last reset date
					lastResetDate = System.DateTime.Now.Date;

					// reset
					TotalCalls = 0;
					TotalAcceptedCalls = 0;
					TotalDroppedCalls = 0;
				}
			}

			/// <summary>
			/// Phone Uri
			/// </summary>
			public System.String PhoneUri { get; private set; }

			/// <summary>
			/// Waiting callers
			/// </summary>
			public System.Int32 Wating { get { return agentHuntList.Count; } }

			/// <summary>
			/// Total calles
			/// </summary>
			public System.Int32 TotalCalls { get; private set; }

			/// <summary>
			/// Dropped callers
			/// </summary>
			public System.Int32 TotalDroppedCalls { get; private set; }

			/// <summary>
			/// Total allocated calls
			/// </summary>
			public System.Int32 TotalAcceptedCalls { get; private set; }

			/// <summary>
			/// Waiting callers
			/// </summary>
			public System.Int64 FirstWatingTime
			{
				get
				{
					// lock access
					// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
					lock (syncAccess)
					{
						// check
						if (agentHuntList.Count > 0)
						{
							return (System.DateTime.Now - agentHuntList[0].CreatedOn).Ticks;
						}
					}

					// none
					return 0;
				}
			}

			/// <summary>
			/// Waiting callers
			/// </summary>
			public System.Int64 AverageWatingTime
			{
				get
				{
					// lock access
					// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
					lock (syncAccess)
					{
						// check any
						if (agentHuntList.Count > 0)
						{
							// init total
							System.Int64 total = 0;

							// add all
							foreach (EFS.RC.CC.Tools.Hunters.Agent agentHunter in agentHuntList)
							{
								total += (System.DateTime.Now - agentHuntList[0].CreatedOn).Ticks;
							}

							// make average
							averageWatingTime = (averageWatingTime + total / agentHuntList.Count) / 2;
						}
					}

					// none
					return averageWatingTime;
				}
			}

			/// <summary>
			/// Add
			/// </summary>
			public void Add(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
			{
				// lock access
				// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
				lock (syncAccess)
				{
					// check
					if (agentHuntList.Contains(agentHunter))
					{
						throw new System.InvalidOperationException("Agent Hunt already in list");
					}

					// add
					agentHuntList.Add(agentHunter);

					// increment total
					TotalCalls++;
				}
			}

			/// <summary>
			/// Update 
			/// </summary>
			public void Update(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
			{
				// lock access
				// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
				lock (syncAccess)
				{
					// check
					if (!agentHuntList.Contains(agentHunter))
					{
						throw new System.InvalidOperationException("Agent Hunt not list");
					}

					// remove
					agentHuntList.Remove(agentHunter);

					// increment dropped
					switch (agentHunter.Status)
					{
						case Agent.HuntStatus.Accepted:
							{
								TotalAcceptedCalls++;
								break;
							}
						case Agent.HuntStatus.Dropped:
							{
								TotalDroppedCalls++;
								break;
							}
					}
				}
			}
		}

		// Counters
		private System.Collections.Generic.Dictionary<EFS.RC.CC.Applications.EndPoints.OfficeService.Customers, Counters> countersMap
			= new System.Collections.Generic.Dictionary<EFS.RC.CC.Applications.EndPoints.OfficeService.Customers, Counters>();

		#endregion

		#region Usage

		/// <summary>
		/// Describe this
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Tools.Hunters.Queue");
		}

		/// <summary>
		/// Register hunter for available agent
		/// </summary>
		/// <param name="agentHunter"></param>
		public void Register(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
		{
			// check
			EFS.Common.Verify.Reference(agentHunter, "agentHunter");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "Register", agentHunter.CustomerSession.CustomerUri, this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// if not re-added
				if (!agentsHuntQueue.Contains(agentHunter))
				{
					// we regieter for termination
					agentHunter.AsinkTerminatedDelegate.Register(OnHunterTerminated);

					// add
					agentsHuntQueue.Add(agentHunter);

					// update counters
					UpdateCounters(agentHunter);

					// log
					EFS.RC.CC.Tools.Loggers.CHL.Add(agentHunter.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_Queued);
				}
			}

			// start mach
			StartMatch();
		}

		/// <summary>
		/// Start mathing
		/// </summary>
		/// <param name="agentHunter"></param>
		internal void StartMatch()
		{	
			// only one thread can run this at any time
			lock (matchSyncAccess)
			{
				// run count
				matchSchedule = true;

				// begin
				if (!matchRunning)
				{
					// mach
					BeginAsyncProcess2(Match);
				}
			}
		}

		/// <summary>
		/// Allocate agent
		/// </summary>
		/// <param name="agentHunter"></param>
		public void AgentAvailable(EFS.RC.CC.Presences.Operators.OfficeService.Agent agent)
		{
			// check
			EFS.Common.Verify.Reference(agent, "agent");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "AgentAvailable", agent.User.SIP, this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				availableAgentsPresences.Add(agent);
			}

			// mach
			StartMatch();
		}

		/// <summary>
		/// Allocate agent
		/// </summary>
		/// <param name="agentHunter"></param>
		public void AgentUnAvailable(EFS.RC.CC.Presences.Operators.OfficeService.Agent agent)
		{
			// check
			EFS.Common.Verify.Reference(agent, "agent");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "AgentUnAvailable", agent.User.SIP, this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (availableAgentsPresences.Contains(agent))
				{
					availableAgentsPresences.Remove(agent);
				}
			}
		}

		/// <summary>
		/// Get hunt queue status message
		/// </summary>
		/// <returns></returns>
		public EFS.RC.CC.Messages.HuntQueue.StatusList GetHuntQueueStatusMessage()
		{
			// make array
			System.Collections.Generic.List<EFS.RC.CC.Messages.HuntQueue.Status> huntQueueStatusList =
				new System.Collections.Generic.List<EFS.RC.CC.Messages.HuntQueue.Status>();

			// make array
			System.Collections.Generic.List<Counters> huntQueueCounters = null;

			// copy
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				huntQueueCounters = new System.Collections.Generic.List<Counters>(countersMap.Values);
			}

			// get all
			foreach (Counters counters in huntQueueCounters)
			{
				// 24h reset
				counters.Do24hReset();

				// send data
				huntQueueStatusList.Add(new EFS.RC.CC.Messages.HuntQueue.Status()
				{
					PhoneUri = counters.PhoneUri,
					WaitCount = counters.Wating,
					LongestWatingTime = counters.FirstWatingTime,
					AverageWatingTime = counters.AverageWatingTime,
					TotalCalls = counters.TotalCalls,
					TotalDroppedCalls = counters.TotalDroppedCalls,
					TotalAcceptedCalls = counters.TotalAcceptedCalls
				});
			}

			// done
			return new EFS.RC.CC.Messages.HuntQueue.StatusList(huntQueueStatusList);
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "OnException", ex, this); 

			// log
			EFS.EventLog.Log.LogException(ex, this);
		}

		/// <summary>
		/// Run
		/// </summary>
		private void Match()
		{
			// manage status and flags
			lock (matchSyncAccess)
			{
				// check running
				if (matchRunning)
				{
					return;
				}

				// set flags
				matchSchedule = false;
				matchRunning = true;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "Match", this); 

			try
			{
				// agents hunter queue
				System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent> localhAgentsHuntQueue = null;

				// agents
				System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent> localAvailableAgentsPresences = null;

				// copy local lists
				lock (syncAccess)
				{
					if (agentsHuntQueue.Count > 0 && availableAgentsPresences.Count > 0)
					{
						localhAgentsHuntQueue = new System.Collections.Generic.List<EFS.RC.CC.Tools.Hunters.Agent>(this.agentsHuntQueue);

						// agents
						localAvailableAgentsPresences = new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent>(this.availableAgentsPresences);
					}
				}

				// theck
				if (null != localhAgentsHuntQueue && null != localAvailableAgentsPresences)
				{
					foreach (EFS.RC.CC.Tools.Hunters.Agent agentHunter in localhAgentsHuntQueue)
					{
						// hunter allocated
						if (null != agentHunter.OperatorSession)
						{
							continue;
						}

						// check any agents available
						if (localAvailableAgentsPresences.Count > 0)
						{
							// get proposed agents
							System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent> proposedAgentsDistrubutions =
								agentHunter.CustomerSession.CustomersOfficeServiceEndPointApplication.AgentsDistribution.Distribute(localAvailableAgentsPresences);

							// sort by idle time
							proposedAgentsDistrubutions.Sort(
								delegate(EFS.RC.CC.Presences.Operators.Distribution.Agent a, EFS.RC.CC.Presences.Operators.Distribution.Agent b)
								{
									if (a.SkillLevel != b.SkillLevel)
									{
										return 0 - a.SkillLevel.CompareTo(b.SkillLevel);
									}
									return a.AgentPresence.IsAvailableSince.CompareTo(b.AgentPresence.IsAvailableSince);
								});


							// found flag
							System.Boolean foundAny = false;

							// get first free agent
							foreach (EFS.RC.CC.Presences.Operators.Distribution.Agent agentPresenceDistribution in proposedAgentsDistrubutions)
							{
								// check in session
								if (agentPresenceDistribution.AgentPresence.InSession)
								{
									continue;
								}

								// try to allocate
								if (agentHunter.TryAllocate(agentPresenceDistribution.AgentPresence))
								{
									foundAny = true;
									break;
								}
							}

							// check if any found
							if (!foundAny && agentHunter.HasIgnoredAgents())
							{
								// clear ignore list
								agentHunter.ClearIgnore();

								// re-start match if any 
								if (proposedAgentsDistrubutions.Count > 0)
								{
									// wait 100 MS before next allocation
									System.Threading.Thread.Sleep(100);

									// start match again
									StartMatch();
								}
							}
						}
						else
						{
							// rtl
							EFS.RC.CC.Tools.Loggers.CHL.Add(agentHunter.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Team_Depleted);
						}
					}
				}
			}
			finally
			{
				// manage status and flags
				lock (matchSyncAccess)
				{
					// remove runnint
					matchRunning = false;

					// start again
					if (matchSchedule)
					{
						// begin
						BeginAsyncProcess2(Match);
					}
				}
			}
		}

		/// <summary>
		/// OnHunterTerminated hunter for available agent
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnRejected(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
		{
			// check
			EFS.Common.Verify.Reference(agentHunter, "agentHunter");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "OnRejected", agentHunter.CustomerSession.CustomerUri, this); 

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// remove
				if (agentsHuntQueue.Contains(agentHunter))
				{
					StartMatch();
				}
			}
		}

		/// <summary>
		/// OnHunterTerminated hunter for available agent
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnHunterTerminated(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
		{
			// check
			EFS.Common.Verify.Reference(agentHunter, "agentHunter");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "OnHunterTerminated", agentHunter.CustomerSession.CustomerUri, this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// remove
				if (agentsHuntQueue.Contains(agentHunter))
				{
					// remove
					agentsHuntQueue.Remove(agentHunter);

					// update counters
					UpdateCounters(agentHunter);
				}
			}
		}

		/// <summary>
		/// Update counters
		/// </summary>
		/// <param name="customersOfficeServiceEndPointApplication"></param>
		private void UpdateCounters(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
		{
			// check
			EFS.Common.Verify.Reference(agentHunter, "agentHunter");

			// add to counters
			Counters counters = null;

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// get counters
				if (countersMap.ContainsKey(agentHunter.CustomerSession.CustomersOfficeServiceEndPointApplication))
				{
					counters = countersMap[agentHunter.CustomerSession.CustomersOfficeServiceEndPointApplication];
				}
				else
				{
					// new counter
					counters = new Counters(agentHunter.CustomerSession.CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.URI);
					countersMap[agentHunter.CustomerSession.CustomersOfficeServiceEndPointApplication] = counters;
				}
			}

			// check status
			if (agentHunter.Status == Agent.HuntStatus.Idle)
			{
				counters.Add(agentHunter);
			}
			else
			{
				counters.Update(agentHunter);
			}
		}

		#endregion
	}
}
 